// Package main is the CLI interface for mobsql consuming the mobsql API.
// See mobsql -h for usage information.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"git.sr.ht/~mil/mobsql"
	"go/doc"
	"go/parser"
	"go/token"
	"gopkg.in/yaml.v3"
	"log"
	"os"
	"runtime/pprof"
	"strings"
)

const (
	docCommand = `Command: should be one of {status,load,compute,purge}; note, you must pass -f when using -c.
---
load: %s

compute: %s

status: %s

purge: %s
`
	docFilter = `YAML filter specification to apply to -c command.
Pass as a YAML object containing optional properties:
  {mdbid, country, municipality, subdivision, provider, name, glob, bbox, maxkm, loaded}
Example YAML filter specifications:
  -f '{}'
  -f 'mdbid: [510, 516]'
  -f 'country: BE'
  -f 'municipality: paris'
  -f 'subdivision: ontario'
  -f 'glob: foo'
  -f 'coords: [[40.512764, -74.251961]]'
  -f 'maxkm: 20'
  -f 'loaded: true'
  -f '{glob: foo, country: US}'
`
)

func main() {
	var (
		usageExamples = `Examples:
  mobsql -f '{}' -c status (View all sources status)
  mobsql -f '{loaded: true}' -c status (View all loaded sources status)
  mobsql -f '{glob: nyc}' -c status (View all sources matching glob of nyc)
  mobsql -f '{country: FR}' -c load (Load all souces matching France country)
  mobsql -f '{country: FR}' -c purge (Purge database of all sources matching France country)
  mobsql -f '{}' -c purge (Purge database of all sources)
  mobsql -h (Display help text)
  mobsql -v (Display version)
		`
		cmd = flag.String("c", "", fmt.Sprintf(
			docCommand,
			extractMobsqlAPIDoc("Load"),
			extractMobsqlAPIDoc("Compute"),
			extractMobsqlAPIDoc("Status"),
			extractMobsqlAPIDoc("Purge"),
		))
		filter         = flag.String("f", "", fmt.Sprintf(docFilter))
		sqliteDBPath   = flag.String("db", "", "Location of SQLite DB path; if unset uses ~/.cache/mobroute/sqlite.db")
		debugVI        = flag.Bool("vi", true, "Enable verbose info messages")
		debugVW        = flag.Bool("vw", false, "Enable verbose warnings messages")
		debugVD        = flag.Bool("vd", false, "Enable verbose debug messages")
		debugVVV       = flag.Bool("vvv", false, "Enable verbose info+warnings+debug messages (equivilant to -vi + -vw + -vd)")
		debugProfile   = flag.String("dprof", "", "Write debug pprof CPU profile to file")
		_              = flag.Bool("h", false, "Display help information")
		cmdInfoVersion = flag.Bool("v", false, "Display version information")
	)
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage:\n  %s [FLAGS]\n\nFlags:\n", os.Args[0])
		flag.PrintDefaults()
		fmt.Fprintln(os.Stderr, usageExamples)
	}
	flag.Parse()

	if *debugProfile != "" {
		if f, err := os.Create(*debugProfile); err != nil {
			log.Fatal(err)
		} else {
			pprof.StartCPUProfile(f)
			defer pprof.StopCPUProfile()
		}
	}

	if (*cmd != "" && *filter == "") || (*cmd == "" && *filter != "") {
		flag.Usage()
		stderrErrorf("Must define -c with -f")
	} else if *cmd == "load" || *cmd == "purge" || *cmd == "status" || *cmd == "compute" {
		var (
			filterStruct mobsql.SourcesetFilter
			err          error
			result       any
			mobsqlConfig = mobsql.Config{
				SQLiteDBPath: sqliteDBPath,
				LogInfo:      *debugVI || *debugVVV,
				LogWarn:      *debugVW || *debugVVV,
				LogDebug:     *debugVD || *debugVVV,
			}
		)

		if err = yaml.Unmarshal([]byte(*filter), &filterStruct); err != nil {
			stderrErrorf("Error demarshalling YML: %v", err)
		} else if *cmd == "purge" {
			result, err = mobsql.Purge(filterStruct, &mobsqlConfig)
		} else if *cmd == "load" {
			result, err = mobsql.Load(filterStruct, &mobsqlConfig)
		} else if *cmd == "compute" {
			result, err = mobsql.Compute(filterStruct, &mobsqlConfig)
		} else if *cmd == "status" {
			result, err = mobsql.Status(filterStruct, &mobsqlConfig)
		}

		if err != nil {
			stderrErrorf("Error performing %s command: %v", *cmd, err)
		} else if bytes, err := json.MarshalIndent(result, "", "  "); err != nil {
			stderrErrorf("Erorr stringifying data: %v", err)
		} else {
			stdout(string(bytes))
		}
	} else if *cmdInfoVersion {
		stdout("0.2.0")
	} else {
		flag.Usage()
	}
}

func stderrInfof(s string, args ...any) {
	log.Printf(s, args...)
}

func stderrErrorf(s string, args ...any) {
	log.Fatalf(s, args...)
}

func stdout(data string) {
	fmt.Println(data)
}

func extractMobsqlAPIDoc(funcName string) string {
	fset := token.NewFileSet()
	if astPkgs, err := parser.ParseDir(fset, ".", nil, parser.ParseComments|parser.DeclarationErrors|parser.AllErrors); err != nil {
		stderrErrorf("Failure to parse mobsql root package: %v", err)
	} else {
		docPkg := doc.New(astPkgs["mobsql"], "", 0)

		for _, f := range docPkg.Types {
			for _, m := range f.Funcs {
				if m.Name == funcName {
					return strings.TrimSuffix(m.Doc, "\n")
				}
			}
		}
		for _, f := range docPkg.Funcs {
			if f.Name == funcName {
				return strings.TrimSuffix(f.Doc, "\n")
			}
		}
	}
	stderrErrorf("Failure to find func in mobsql root package: %v", funcName)
	return ""
}
